/* 
   A simple server in the internet domain using TCP
Usage:./server port (E.g. ./server 10000
*/
#include <stdio.h>
#include <sys/types.h>   
#include <sys/socket.h>
#include <sys/time.h>
#include <time.h>  
#include <netinet/in.h>  
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include "packet.h"
//#include "host.h"

#define TIME_OUT 3
#define BUF_SIZE 256
#define SEQ_POS 0
#define ACK_POS 1
#define SUM_POS 2
#define DATA_POS 3
#define MAX_SENT 5

void error(char *msg)
{
   perror(msg);
   exit(1);
}


void buildPacket(char *buf, packet* new_pck);


// send file with GBN protocol
int sendfileGBN(FILE* fd, int sockfd, struct sockaddr_in their_addr,
      socklen_t addr_len, double pl, double pc, int cwnd);

// calculate pl and pc
// return -1 if loss
// return -2 if coruption
// return 0 if normal
int calculateP(double pl, double pc); 

int rcv_with_timeout(int sockfd, packet* pck,struct sockaddr_in their_addr,
      socklen_t addr_len);

int sending(packet* pck_array, int sockfd, struct sockaddr_in their_addr,
      socklen_t addr_len, int num_packets)
{
   printf("Go inside this \n");
   //packet* new_pck;
   int n;
   for(n = 0; n < num_packets; n++)
   {
      printf("Packet checksum %d\n", pck_array[n].checksum);
      printf("Packet payload: %s \n", pck_array[n].payload);
      //new_pck = &pck_array[n];
      int flag = sendto(sockfd,&pck_array[n],sizeof(packet),0,
            (struct sockaddr*)&their_addr, addr_len);
      if(flag == -1)
      {
         perror("send packet");
         exit(2);
      }
      printf("Sent: packet number: %d\n", n);
      printf("Packet checksum %d\n", pck_array[n].checksum);
      printf("Packet payload: %s \n", pck_array[n].payload);
      printf("\n\n");
   }

   return 0;
}

int main(int argc, char *argv[])
{
   // variable declaration
   int sockfd; // socket descriptors 
   int portno; // port number
   socklen_t clilen;

   char buf[256];

   /*sockaddr_in: Structure Containing an Internet Address*/
   struct sockaddr_in serv_addr, cli_addr;

   //int n;

   // NEW VARIABLE DECALRETION for PROJECT2
   socklen_t addr_len;
   double pc,pl,cwnd2; // probability
   //int cwnd;
   FILE* fd; // file descriptor

   srand(time(0));
   // Variable for select()
   fd_set master;    // master file descriptor list
   fd_set read_fds;  // temp file descriptor list for select()
   int fdmax;        // maximum file descriptor number

   // 
   if (argc != 2 && argc != 5) 
   {
      fprintf(stderr,"ERROR, no port provided.\n\tExample:./sender <portnumber> OR ./sender <portnumber> CWnd Pl Pc\n");
      exit(1);
   } 
   else if (argc == 2) 
   {
      portno = atoi(argv[1]);
      //cwnd   = 3; // dont know why, but this line break the prgram
      cwnd2 = 5;
      pl     = 0;
      pc     = 0;
   } 
   else if (argc == 5) 
   {
      portno = atoi(argv[1]);
      //cwnd   = atoi(argv[2]); // same as above
      cwnd2 = atoi(argv[2]);
      pl     = strtod(argv[3],0);
      pc     = strtod(argv[4],0);
   }

   // create a new socket
   sockfd = socket(AF_INET, SOCK_DGRAM, 0);
   if (sockfd < 0) 
      error("ERROR opening socket");

   bzero((char *) &serv_addr, sizeof(serv_addr));
   portno = atoi(argv[1]); //atoi converts from String to Integer
   serv_addr.sin_family = AF_INET;
   serv_addr.sin_addr.s_addr = INADDR_ANY; 
   serv_addr.sin_port = htons(portno); 


   // TODO 
   // set their_addr and smt
   addr_len = sizeof(struct sockaddr);

   //Bind the socket to the server address
   if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) 
      error("ERROR on binding");

   // MAIN PROCESS
   FD_ZERO(&master);    // clear the master and temp sets
   FD_ZERO(&read_fds);

   // add socket to the master set
   //FD_SET(sockfd, &master);
   fdmax = sockfd; // biggest fd

   clilen = sizeof(cli_addr);

   int num_packets;
   //main loop
   while(1)
   {
      //read_fds = master; // copy it
      // the select() stuff
      //if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) 
      //{
      //  perror("select");
      //  exit(4);
      //}

      //if (FD_ISSET(sockfd, &read_fds)) // smt to read
      //{

      // TODO
      // main logic should be here
      // receive the file and check for the checksum
      // for the simpllest case
      // GBN will do later on
      //int rcv_flag;
      int rcv_flag = recvfrom(sockfd,buf,BUF_SIZE,0,
            (struct sockaddr*)&cli_addr,
            &addr_len);
      if (rcv_flag == -1)
      {
         perror("ERROR: recvfrom");
         exit(3);
      }

      packet pck;
      buildPacket(buf, &pck);
      packet *new_pck = &pck;

      if (new_pck->seqnum == -99) // a request
      {
         strcpy(buf, new_pck->payload);
         printf("Received request for %s\n", buf);
      }

      // Open file and checking
      fd = fopen(buf, "r");
      // If file exist, open and send back number of packet will be send
      // as well as a buffer called "EXIST"
      // If file do not exit, send back "DNE"
      if (fd == NULL) // file does not exist
      {
         fprintf(stderr, "%s not found on sender side\n", buf);
         new_pck->seqnum = -100; // mean file does not exist
         strcpy(new_pck->payload, "DNE");
         sendto(sockfd,new_pck,sizeof(packet),0,
               (struct sockaddr*)&cli_addr, addr_len);
         printf("Sent: file not found\n");
         continue;
      }
      else
      {
         //fclose(fd);
         printf("%s found on this server \n", buf);


         // get file size
         fseek (fd, 0, SEEK_END);
         int file_size = ftell(fd);
         rewind(fd);

         // after calling sendfile, pck_array contain an array of packet
         // ready to be sent
         num_packets = (int)((file_size/PACKET_SIZE) + 1);
         new_pck->seqnum = num_packets; //send along with the number of pck
         strcpy(new_pck->payload, "EXIST");
         sendto(sockfd,new_pck,sizeof(packet),0,
               (struct sockaddr*)&cli_addr, addr_len);
         printf("Sent an EXIST....\n");
      }

      // Waitng for an "REQ_ACK" from receiver before processing
      while(1)
      {
         rcv_flag = recvfrom(sockfd,new_pck,sizeof(packet),0,
               (struct sockaddr*)&cli_addr,
               &addr_len);
         if (rcv_flag == -1)
         {
            perror("ERROR: recvfrom");
            exit(3);
         }
         if (strcmp(new_pck->payload, "REQ_ACK") == 0) // REQ_ACK packet
         {
            printf("Received REQ_ACK\n");
            printf("Start sending file\n");
            break;
         }
         else // not a REQ_ACK, drop it and continue to wait
         {
            printf("Received not REQ_ACK\n");
            printf("Keep waiting\n");
            continue;
         }
      } // end wating for "REQ_ACK"
      /* End of send and request file section */


      /* SENDING FILE SECTION */
      //sendfile(fd, sockfd, cli_addr, addr_len);
      sendfileGBN(fd, sockfd, cli_addr, addr_len, pl,pc, (int)cwnd2);

      printf("FILE SENT TO THE SERVER\n");
      //free(pck_array);
      /* END SEND FILE SECTION */

      fclose(fd); // close file
   } // end main loop

   /* close(sockfd); */

   return 0; 
}


// send file with GBN protocol
int sendfileGBN(FILE* fd, int sockfd, struct sockaddr_in their_addr,
      socklen_t addr_len, double pl, double pc, int cwnd)
{
   // main variable declaration
   int file_size;
   int num_packets;
   packet* packetArray;
   int i;
   time_t start, end;

   // optional/testing variable declaration

   printf("Sending file...\n");

   // get file size
   fseek (fd, 0, SEEK_END);
   file_size = ftell(fd);
   rewind(fd);

   num_packets = (int)((file_size/PACKET_SIZE) + 1);
   printf("Filesize: %d\n", file_size);
   printf("Total number of packets: %i\n", num_packets);

   packetArray = (packet *)malloc(num_packets*(sizeof(packet)));

   for(i = 0; i < num_packets; i++)
   {
      createPacket(&packetArray[i], fd, 0);
   }

   // GBN HERE
   int base = 0, ack = 0;
   /* int ack_corrupted = 0; */
   i = 0;
   int num_sent = 0;
   time(&start);
   int last_acked = 0;

   int t = 0; // testing purpose

   while (base < num_packets)
   {

      // send all the packet from [base, cwnd]
      while(i - base < cwnd && i < num_packets)
      {
         // set ack and checksum
         packetArray[i].seqnum = i;
         packetArray[i].acknum = i;
         packetArray[i].checksum = calculateChecksum(&packetArray[i]);

         // calculate the probability of loss, corruption
         // if corruption, modify the checksum before sending
         // the ACK receive will indicate that the packet is corrupted

         /** NEW CHANGES HERE **/
         int prob = calculateP(pl, pc);
         if (prob == -1) // loss packet
	   {
	     printf("Packet number %d will be LOST\n", i);
	   }
         else if (prob == -2) // corrupted, modify the checksum
	   {
	     //packetArray[i].checksum = 0;
	     packetArray[i].payload[0] += 1;
	     printf("Packet number %d will be CORRUPTED\n", i);
	   }

         /* END */
         if (i == base) // new chunk of packets sent,need to restart timer
         {
            time(&start);
         }

         time(&start);
         // only send if packet does not lost
         if (prob != -1)
         {
	   int flag;
	   //if(prob == -2) // reverse the corruption
	       // {
	   // packet c_pack;
	   // }
	   //else
	   //{
	       flag = sendto(sockfd,&packetArray[i],sizeof(packet),0,
                  (struct sockaddr*)&their_addr, addr_len);
	       // }
            if(flag == -1)
            {
               perror("send packet");
               exit(2);
            }
            printf("Sent: packet number: %d\n", i);
            printf("Packet checksum %d\n", packetArray[i].checksum);
            //printf("Packet payload: %s \n", packetArray[i].payload);
            printf("=========================\n");
	    if(prob == -2)
	      {
		 packetArray[i].payload[0] -= 1;
	      }
         }
         i++;
         ack++;
      }

      time(&end);
      if (num_sent > MAX_SENT) // only sent a fixed number of time
	{
	  printf("Too many failed attempt sending, assume receiver disconnected\n");
	  break;
	}
      if (difftime(end, start) > TIME_OUT) // time out
      {
         i = base;
	 num_sent++; // increase number of time sent
	 printf("Value of num_sent: %d\n", num_sent);
         printf("Time out occurs for packet number %d\n", base);
         continue; // go to next loop, resend all the packet from [base,cwnd]
         // count ack
      }
      // wait till received the ACK until doing the next send
      //printf("WAITING after sent packet %d\n", i-1);
      printf("WAITING for ACK[%d]\n", base);
      packet new_pck; // create new packet in order to save if necessary
      t = rcv_with_timeout(sockfd, &new_pck, their_addr,
            addr_len);
      if (t != -1) // received smt
	{
	  num_sent = 0; // reset num_sent if necessary
	}
      /* LOSS AND CORRUPTION */
      if (new_pck.seqnum == -2) // indicate packet corruption
      {
	//t = 1; // testing
         printf("Received: CORRUPTION ACK for packet[%d]\n", last_acked+1);
         i = base;

         continue; // start the next loop, mean resent the packet
      }
      /* END */

      /* ACK LOSS OR CORRUPTION */
      if (new_pck.acknum == CORRUPTED_ACK)
	{
	  printf("Received: CORRUPTED ACK\n");
	} 
      // receive ACK successfully
      else
	{
	 printf("Received: ACK for packet[%d]\n", new_pck.acknum);
	 last_acked = new_pck.acknum;
	}
      if (base <= new_pck.acknum) // correctly packet
      {
         base = new_pck.acknum + 1; // base = oldest unACK packet
         //i = base;
         time(&start); // restart timer
      }
      if(new_pck.acknum == num_packets) // file received
	{
	  printf("Receiver received the file\n");
	}
   }

   free(packetArray);

   return num_packets;

   return 0;
}

// calculate pl and pc
// return -1 if loss
// return -2 if coruption
// return 0 if normal
int calculateP(double pl, double pc)
{
   if ((double)(rand()%100) < (pl*100)) // loss       
   {
      printf("Packet loss\n");
      return -1;
   }
   else if ((double)(rand()%100) < (pc*100)) // coruption 
   {
      printf("Packet corruption\n");
      return -2;
   }
   else // normal
   {
      return 0;
   }

   return 0;
}


int rcv_with_timeout(int sockfd, packet* pck, struct sockaddr_in their_addr,
      socklen_t addr_len)
{
   struct timeval tv;
   fd_set readfds;

   tv.tv_sec = 0;
   tv.tv_usec = 500000;

   FD_ZERO(&readfds);
   FD_SET(sockfd, &readfds);

   // don't care about writefds and exceptfds:
   select(sockfd+1, &readfds, NULL, NULL, &tv);

   if (FD_ISSET(sockfd, &readfds))
   {
      int flag = recvfrom(sockfd,pck,sizeof(packet),0,
            (struct sockaddr*)&their_addr,
            &addr_len);
      if(flag == -1)
      {
         perror("receive ACK");
         exit(2);
      }
   }
   else
   {
      printf("Timed out.\n");
      return -1;
   }
   return 0;
}

void buildPacket(char *buf, packet *new_pck)
{
   (*new_pck).seqnum   = buf[SEQ_POS]; //Sequency number
   (*new_pck).acknum   = buf[ACK_POS]; //Ack number
   (*new_pck).checksum = buf[SUM_POS]; // Checksum
   strcpy((*new_pck).payload, buf+DATA_POS+9);
}

